package cn.huaqingcheng.tianshu.security.web;

import cn.huaqingcheng.tianshu.security.annotation.PermitAnonymous;
import cn.huaqingcheng.tianshu.security.config.CustomAuthorizeHttpRequests;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import jakarta.annotation.PostConstruct;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
 * 允许匿名访问授权
 * <p>
 * 实现 {@link PermitAnonymous} 以允许在方法级别声明自定义的匿名访问, 使用 {@link PermitAnonymous#ROLE_NAME} 角色定义 <br>
 * 同时覆盖全局规则为: {@code 完整验证 && 非匿名}
 * <p>
 * 这个实现必须放在最后执行, 因为 {@code anyRequest} 必须最后配置
 */
@Slf4j
@Order
@Component
@EqualsAndHashCode
public class PermitAnonymousAccessAuthorize implements CustomAuthorizeHttpRequests {

    private final RequestMappingHandlerMapping requestMappingHandlerMapping;

    private final Map<HttpMethod, Set<String>> allUrl = new ConcurrentHashMap<>();

    public PermitAnonymousAccessAuthorize(@Qualifier("requestMappingHandlerMapping") RequestMappingHandlerMapping requestMappingHandlerMapping) {
        this.requestMappingHandlerMapping = requestMappingHandlerMapping;
    }


    @PostConstruct
    public void init() {
        var map = new RequestMappingFind(requestMappingHandlerMapping)
                .find(PermitAnonymous.class);
        allUrl.putAll(map);
    }

    @Override
    public void apply(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
        for (var entry : allUrl.entrySet()) {
            HttpMethod method = entry.getKey();
            String[] urls = entry.getValue().toArray(new String[0]);

            log.trace("允许匿名访问 (@PermitAnonymous): {} {}", method, urls);

            registry.requestMatchers(method, urls).hasRole(PermitAnonymous.ROLE_NAME);
        }

        // 覆盖全局规则为: 完整验证 且 非匿名
        registry.anyRequest().access(new AnonymousTokenAuthenticatedAuthorizationManager());
    }


    /**
     * 匿名授权管理器
     * <p>
     * 要求 {@code isFullyAuthenticated && !hasAnyRole(PermitAnonymous.ROLE_NAME)}
     */
    public static class AnonymousTokenAuthenticatedAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

        private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

        private final AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAnyRole(PermitAnonymous.ROLE_NAME);

        @Override
        public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, RequestAuthorizationContext object) {
            Authentication authentication = authenticationSupplier.get();
            if (authentication == null) {
                log.trace("禁止访问 没有任何认证信息");
                return new AuthorizationDecision(false);
            }

            AuthorizationResult authorize = manager.authorize(authenticationSupplier, object);
            // 如果拥有匿名角色 拒绝
            if (authorize != null && authorize.isGranted()) {
                log.trace("禁止匿名访问");
                return new AuthorizationDecision(false);
            }

            // 必须完整验证
            AuthorizationDecision decision = new AuthorizationDecision(trustResolver.isFullyAuthenticated(authentication));
            if (log.isTraceEnabled() && !decision.isGranted()) {
                log.trace("未授权 禁止访问");
            }

            return decision;
        }

    }

}
